#ifndef EXTRUDER_H_INCLUDED
#define EXTRUDER_H_INCLUDED

#define CELSIUS_EXTRA_BITS 3
#define VIRTUAL_EXTRUDER 16 // don't change this to more then 16 without modifying the eeprom positions

//#if TEMP_PID
//extern uint8_t current_extruder_out;
//#endif

// Updates the temperature of all extruders and heated bed if it's time.
// Toggles the heater power if necessary.
extern bool reportTempsensorError(); ///< Report defect sensors
extern uint8_t manageMonitor;
#define HTR_OFF 0
#define HTR_PID 1
#define HTR_SLOWBANG 2
#define HTR_DEADTIME 3

#define TEMPERATURE_CONTROLLER_FLAG_ALARM 1
#define TEMPERATURE_CONTROLLER_FLAG_DECOUPLE_FULL 2  //< Full heating enabled
#define TEMPERATURE_CONTROLLER_FLAG_DECOUPLE_HOLD 4  //< Holding target temperature
#define TEMPERATURE_CONTROLLER_FLAG_SENSDEFECT    8  //< Indicating sensor defect
#define TEMPERATURE_CONTROLLER_FLAG_SENSDECOUPLED 16 //< Indicating sensor decoupling
#define TEMPERATURE_CONTROLLER_FLAG_JAM           32 //< Indicates a jammed filament
#define TEMPERATURE_CONTROLLER_FLAG_SLOWDOWN      64 //< Indicates a slowed down extruder

/** TemperatureController manages one heater-temperature sensor loop. You can have up to
4 loops allowing pid/bang bang for up to 3 extruder and the heated bed.

*/
class TemperatureController
{
public:
	uint8_t pwmIndex; ///< pwm index for output control. 0-2 = Extruder, 3 = Fan, 4 = Heated Bed
	uint8_t sensorType; ///< Type of temperature sensor.
	uint8_t sensorPin; ///< Pin to read extruder temperature.
	int8_t heatManager; ///< How is temperature controlled. 0 = on/off, 1 = PID-Control, 3 = dead time control
	int16_t currentTemperature; ///< Current temperature value read from sensor.
	//int16_t targetTemperature; ///< Target temperature value in units of sensor.
	float currentTemperatureC; ///< Current temperature in degC.
	float targetTemperatureC; ///< Target temperature in degC.
	uint32_t lastTemperatureUpdate; ///< Time in millis of the last temperature update.
#if TEMP_PID
	float tempIState; ///< Temp. var. for PID computation.
	uint8_t pidDriveMax; ///< Used for windup in PID calculation.
	uint8_t pidDriveMin; ///< Used for windup in PID calculation.
#define deadTime pidPGain
	// deadTime is logically different value but physically overlays pidPGain for saving space
	float pidPGain; ///< Pgain (proportional gain) for PID temperature control [0,01 Units].
	float pidIGain; ///< Igain (integral) for PID temperature control [0,01 Units].
	float pidDGain;  ///< Dgain (damping) for PID temperature control [0,01 Units].
	uint8_t pidMax; ///< Maximum PWM value, the heater should be set.
	float tempIStateLimitMax;
	float tempIStateLimitMin;
	uint8_t tempPointer;
	float tempArray[4];
#endif
	uint8_t flags;
	millis_t lastDecoupleTest;  ///< Last time of decoupling sensor-heater test
	float  lastDecoupleTemp;  ///< Temperature on last test
	millis_t decoupleTestPeriod; ///< Time between setting and testing decoupling.

	void setTargetTemperature(float target);
	void updateCurrentTemperature();
	void updateTempControlVars();

	inline bool isAlarm()
	{
		return flags & TEMPERATURE_CONTROLLER_FLAG_ALARM;
	}
	inline void setAlarm(bool on)
	{
		if (on) flags |= TEMPERATURE_CONTROLLER_FLAG_ALARM;
		else flags &= ~TEMPERATURE_CONTROLLER_FLAG_ALARM;
	}
	inline bool isDecoupleFull()
	{
		return flags & TEMPERATURE_CONTROLLER_FLAG_DECOUPLE_FULL;
	}
	inline void removeErrorStates() {
		flags &= ~(TEMPERATURE_CONTROLLER_FLAG_ALARM | TEMPERATURE_CONTROLLER_FLAG_SENSDEFECT | TEMPERATURE_CONTROLLER_FLAG_SENSDECOUPLED);
	}
	inline bool isDecoupleFullOrHold()
	{
		return flags & (TEMPERATURE_CONTROLLER_FLAG_DECOUPLE_FULL | TEMPERATURE_CONTROLLER_FLAG_DECOUPLE_HOLD);
	}
	inline void setDecoupleFull(bool on)
	{
		flags &= ~(TEMPERATURE_CONTROLLER_FLAG_DECOUPLE_FULL | TEMPERATURE_CONTROLLER_FLAG_DECOUPLE_HOLD);
		if (on) flags |= TEMPERATURE_CONTROLLER_FLAG_DECOUPLE_FULL;
	}
	inline bool isDecoupleHold()
	{
		return flags & TEMPERATURE_CONTROLLER_FLAG_DECOUPLE_HOLD;
	}
	inline void setDecoupleHold(bool on)
	{
		flags &= ~(TEMPERATURE_CONTROLLER_FLAG_DECOUPLE_FULL | TEMPERATURE_CONTROLLER_FLAG_DECOUPLE_HOLD);
		if (on) flags |= TEMPERATURE_CONTROLLER_FLAG_DECOUPLE_HOLD;
	}
	inline void startFullDecouple(millis_t &t)
	{
		if (isDecoupleFull()) return;
		lastDecoupleTest = t;
		lastDecoupleTemp = currentTemperatureC;
		setDecoupleFull(true);
	}
	inline void startHoldDecouple(millis_t &t)
	{
		if (isDecoupleHold()) return;
		if (fabs(currentTemperatureC - targetTemperatureC) + 1 > DECOUPLING_TEST_MAX_HOLD_VARIANCE) return;
		lastDecoupleTest = t;
		lastDecoupleTemp = targetTemperatureC;
		setDecoupleHold(true);
	}
	inline void stopDecouple()
	{
		setDecoupleFull(false);
	}
	inline bool isSensorDefect()
	{
		return flags & TEMPERATURE_CONTROLLER_FLAG_SENSDEFECT;
	}
	inline bool isSensorDecoupled()
	{
		return flags & TEMPERATURE_CONTROLLER_FLAG_SENSDECOUPLED;
	}
	static void resetAllErrorStates();
#if EXTRUDER_JAM_CONTROL
	inline bool isJammed()
	{
		return flags & TEMPERATURE_CONTROLLER_FLAG_JAM;
	}
	void setJammed(bool on);
	inline bool isSlowedDown()
	{
		return flags & TEMPERATURE_CONTROLLER_FLAG_SLOWDOWN;
	}
	inline void setSlowedDown(bool on)
	{
		flags &= ~TEMPERATURE_CONTROLLER_FLAG_SLOWDOWN;
		if (on) flags |= TEMPERATURE_CONTROLLER_FLAG_SLOWDOWN;
	}

#endif
	void waitForTargetTemperature();
#if TEMP_PID
	void autotunePID(float temp, uint8_t controllerId, int maxCycles, bool storeResult);
#endif
};
class Extruder;
extern Extruder extruder[];

#if EXTRUDER_JAM_CONTROL
#if JAM_METHOD == 1
#define _TEST_EXTRUDER_JAM(x,pin) {\
        uint8_t sig = READ(pin);extruder[x].jamStepsSinceLastSignal += extruder[x].jamLastDir;\
        if(extruder[x].jamLastSignal != sig && abs(extruder[x].jamStepsSinceLastSignal - extruder[x].jamLastChangeAt) > JAM_MIN_STEPS) {\
          if(sig) {extruder[x].resetJamSteps();} \
          extruder[x].jamLastSignal = sig;extruder[x].jamLastChangeAt = extruder[x].jamStepsSinceLastSignal;\
        } else if(abs(extruder[x].jamStepsSinceLastSignal) > JAM_ERROR_STEPS && !Printer::isDebugJamOrDisabled() && !extruder[x].tempControl.isJammed()) \
            extruder[x].tempControl.setJammed(true);\
    }
#define RESET_EXTRUDER_JAM(x,dir) extruder[x].jamLastDir = dir ? 1 : -1;
#elif JAM_METHOD == 2
#define _TEST_EXTRUDER_JAM(x,pin) {\
        uint8_t sig = READ(pin);\
          if(sig){extruder[x].tempControl.setJammed(true);} else if(!Printer::isDebugJamOrDisabled() && !extruder[x].tempControl.isJammed()) {extruder[x].resetJamSteps();}}
#define RESET_EXTRUDER_JAM(x,dir)
#elif JAM_METHOD == 3
#define _TEST_EXTRUDER_JAM(x,pin) {\
        uint8_t sig = !READ(pin);\
          if(sig){extruder[x].tempControl.setJammed(true);} else if(!Printer::isDebugJamOrDisabled() && !extruder[x].tempControl.isJammed()) {extruder[x].resetJamSteps();}}
#define RESET_EXTRUDER_JAM(x,dir)
#else
#error Unknown value for JAM_METHOD
#endif
#define ___TEST_EXTRUDER_JAM(x,y) _TEST_EXTRUDER_JAM(x,y)
#define __TEST_EXTRUDER_JAM(x) ___TEST_EXTRUDER_JAM(x,EXT ## x ## _JAM_PIN)
#define TEST_EXTRUDER_JAM(x) __TEST_EXTRUDER_JAM(x)
#else
#define TEST_EXTRUDER_JAM(x)
#define RESET_EXTRUDER_JAM(x,dir)
#endif

#define EXTRUDER_FLAG_RETRACTED 1
#define EXTRUDER_FLAG_WAIT_JAM_STARTCOUNT 2 ///< Waiting for the first signal to start counting

/** \brief Data to drive one extruder.

This structure contains all definitions for an extruder and all
current state variables, like current temperature, feeder position etc.
*/
class Extruder   // Size: 12*1 Byte+12*4 Byte+4*2Byte = 68 Byte
{
public:
	static Extruder *current;
#if FEATURE_DITTO_PRINTING
	static uint8_t dittoMode;
#endif
#if MIXING_EXTRUDER > 0
	static int mixingS; ///< Sum of all weights
	static uint8_t mixingDir; ///< Direction flag
	static uint8_t activeMixingExtruder;
	static void recomputeMixingExtruderSteps();
#endif
	uint8_t id;
	int32_t xOffset;
	int32_t yOffset;
	int32_t zOffset;
	float stepsPerMM;        ///< Steps per mm.
	int8_t enablePin;          ///< Pin to enable extruder stepper motor.
//  uint8_t directionPin; ///< Pin number to assign the direction.
//  uint8_t stepPin; ///< Pin number for a step.
	uint8_t enableOn;
	//  uint8_t invertDir; ///< 1 if the direction of the extruder should be inverted.
	float maxFeedrate;      ///< Maximum feedrate in mm/s.
	float maxAcceleration;  ///< Maximum acceleration in mm/s^2.
	float maxStartFeedrate; ///< Maximum start feedrate in mm/s.
	int32_t extrudePosition;   ///< Current extruder position in steps.
	int16_t watchPeriod;        ///< Time in seconds, a M109 command will wait to stabalize temperature
	int16_t waitRetractTemperature; ///< Temperature to retract the filament when waiting for heatup
	int16_t waitRetractUnits;   ///< Units to retract the filament when waiting for heatup
#if USE_ADVANCE
#if ENABLE_QUADRATIC_ADVANCE
	float advanceK;         ///< Koefficient for advance algorithm. 0 = off
#endif
	float advanceL;
	int16_t advanceBacklash;
#endif // USE_ADVANCE
#if MIXING_EXTRUDER > 0
	int mixingW;   ///< Weight for this extruder when mixing steps
	int mixingE;   ///< Cumulated error for this step.
	int virtualWeights[VIRTUAL_EXTRUDER]; // Virtual extruder weights
#endif // MIXING_EXTRUDER > 0
	TemperatureController tempControl;
	const char * PROGMEM selectCommands;
	const char * PROGMEM deselectCommands;
	uint8_t coolerSpeed; ///< Speed to use when enabled
	uint8_t coolerPWM; ///< current PWM setting
	float diameter;
	uint8_t flags;
#if EXTRUDER_JAM_CONTROL
	int16_t jamStepsSinceLastSignal; // when was the last signal
	uint8_t jamLastSignal; // what was the last signal
	int8_t jamLastDir;
	int16_t jamStepsOnSignal;
	int16_t jamLastChangeAt;
#endif

	// Methods here

#if EXTRUDER_JAM_CONTROL
	inline bool isWaitJamStartcount()
	{
		return flags & EXTRUDER_FLAG_WAIT_JAM_STARTCOUNT;
	}
	inline void setWaitJamStartcount(bool on)
	{
		if (on) flags |= EXTRUDER_FLAG_WAIT_JAM_STARTCOUNT;
		else flags &= ~(EXTRUDER_FLAG_WAIT_JAM_STARTCOUNT);
	}
	static void markAllUnjammed();
	void resetJamSteps();
#endif
#if MIXING_EXTRUDER > 0
	static void setMixingWeight(uint8_t extr, int weight);
#endif
	static void step();
	static void unstep();
	static void setDirection(uint8_t dir);
	static void enable();
#if FEATURE_RETRACTION
	inline bool isRetracted() { return (flags & EXTRUDER_FLAG_RETRACTED) != 0; }
	inline void setRetracted(bool on) {
		flags = (flags & (255 - EXTRUDER_FLAG_RETRACTED)) | (on ? EXTRUDER_FLAG_RETRACTED : 0);
	}
	void retract(bool isRetract, bool isLong);
	void retractDistance(float dist);
#endif
	static void manageTemperatures();
	static void disableCurrentExtruderMotor();
	static void disableAllExtruderMotors();
	static void selectExtruderById(uint8_t extruderId);
	static void disableAllHeater();
	static void initExtruder();
	static void initHeatedBed();
	static void setHeatedBedTemperature(float temp_celsius, bool beep = false);
	static float getHeatedBedTemperature();
	static void setTemperatureForExtruder(float temp_celsius, uint8_t extr, bool beep = false, bool wait = false);
	static void pauseExtruders();
	static void unpauseExtruders();
	inline static int getCurrentTemp(int ext)
	{
		Extruder *actExtruder = &extruder[ext];
		return actExtruder->tempControl.currentTemperatureC;
	};
	inline static int getTargetTemp(int ext)
	{
		Extruder *actExtruder = &extruder[ext];
		return actExtruder->tempControl.targetTemperatureC;
	};
	inline static int getTempDiff(int ext)
	{
		Extruder *actExtruder = &extruder[ext];
		return abs(actExtruder->tempControl.targetTemperatureC - actExtruder->tempControl.currentTemperatureC);
	}
};

#if HAVE_HEATED_BED
#define HEATED_BED_INDEX NUM_EXTRUDER
extern TemperatureController heatedBedController;
#else
#define HEATED_BED_INDEX NUM_EXTRUDER-1
#endif
#if FAN_THERMO_PIN > -1
#define THERMO_CONTROLLER_INDEX HEATED_BED_INDEX+1
extern TemperatureController thermoController;
#else
#define THERMO_CONTROLLER_INDEX HEATED_BED_INDEX
#endif
#define NUM_TEMPERATURE_LOOPS THERMO_CONTROLLER_INDEX+1

#define TEMP_INT_TO_FLOAT(temp) ((float)(temp)/(float)(1<<CELSIUS_EXTRA_BITS))
#define TEMP_FLOAT_TO_INT(temp) ((int)((temp)*(1<<CELSIUS_EXTRA_BITS)))

//extern Extruder *Extruder::current;
#if NUM_TEMPERATURE_LOOPS > 0
extern TemperatureController *tempController[NUM_TEMPERATURE_LOOPS];
#endif
extern uint8_t autotuneIndex;

#endif // EXTRUDER_H_INCLUDED
